package br.com.unifebe.tcc.componente.GUI;

import java.awt.Toolkit;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;

import javax.swing.InputVerifier;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.log4j.Logger;

public class JMLDoubleEdit extends JTextField implements FocusListener {

	private Toolkit toolkit;
	private DecimalFormat decimalFormatter;
	private String mascara;
	private int casasDecimais; 
	private char separadorDecimal = Parametros.getFormatSimbols().getDecimalSeparator();
	private double maxDouble = Double.MAX_VALUE;
	private double minDouble = Double.MIN_VALUE;
	private boolean isFormatting = false;
	private static Logger logger = Logger.getLogger( JMLDoubleEdit.class );
	private Collection<InputVerifier> inputVerifiers = new ArrayList<InputVerifier>();



	public JMLDoubleEdit () {
		this(0, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY , "#,##0.00", 2);
	}

	public JMLDoubleEdit (double value) {
		this(value, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY , "", 0);
	}

	public JMLDoubleEdit (double value, double max) {
		this(value, max, Double.NEGATIVE_INFINITY , "", 0);
	}

	public JMLDoubleEdit (double value, String mascara, int casasDecimais) {
		this(value, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY , mascara, casasDecimais);
	}

	public JMLDoubleEdit (double value, double max, String mascara, int casasDecimais) {
		this(value, max, Double.NEGATIVE_INFINITY , mascara, casasDecimais);
	}

	public JMLDoubleEdit (double value, double max, double min, String mascara, int casasDecimais) {
		logger.debug("Criando doubleEdit max="+max+" min="+min+" value="+value+" mascara="+mascara);
		this.maxDouble = max;
		this.minDouble =  min;
		setCasasDecimais( casasDecimais );
		setMascara( mascara );

		setHorizontalAlignment( SwingConstants.RIGHT );
		setValue( value );

		logger.debug("Adicionando focusListener");
		addFocusListener( this );
		logger.debug("Adicionando o controller dos verifier");
		setInputVerifier( new InputVerifierController() );
		logger.debug("Adicionado verifier de controle dos valores de maximo e minimos");
		addInputVerifier( new MinMaxVerifier() );
		this.setFont(Parametros.getFontEdit());

		this.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				if ( e.getKeyCode() == 10 ) {
					JComponent comp = ( (JComponent) GUIUtils.findNextFocus() ); 
					if (comp != null) {
						comp.requestFocus();
						comp.grabFocus();
					}
				}
			}
		});
	}

	public boolean addInputVerifier(InputVerifier inputVerifier) {
		if (inputVerifier != null) {
			logger.debug("Adicionando verifier "+inputVerifier);
			return inputVerifiers.add( inputVerifier );
		}
		return false;
	}

	public boolean removeInputVerifier(InputVerifier inputVerifier) {
		if (inputVerifier != null) {
			logger.debug("Removendo verifier "+inputVerifier);
			return inputVerifiers.remove( inputVerifier );
		}
		return false;
	}


	public void setMascara(String mascara, int casasDecimais) {
		setFormato(mascara, casasDecimais);
	}

	public void setFormato(String mascara, int casasDecimais) {
		setCasasDecimais( casasDecimais );
		setMascara( mascara );
		setText( getText() );
	}

	public void setCasasDecimais(int casasDecimais) {
		this.casasDecimais = casasDecimais;
	}

	public int getCasasDecimais() {
		return casasDecimais;
	}

	protected void setMascara(String mascara) {
		this.mascara = mascara;
		decimalFormatter = null;
	}

	public String getMascara() {
		if ( mascara==null || mascara.equals("") ) {
			StringBuilder  sb = new StringBuilder();
			sb.append("0");
			if ( getCasasDecimais() > 0 ) {
				sb.append(".");
				for( int i=0;i<getCasasDecimais();i++ ) {
					sb.append("0");
				}
			}
			mascara = sb.toString();
		}
		return mascara;
	}

	public DecimalFormat getDecimalFormatter() {
		if (decimalFormatter == null) {
			decimalFormatter  = new DecimalFormat( getMascara() );
			decimalFormatter.setDecimalFormatSymbols( Parametros.getFormatSimbols() );
		}
		return decimalFormatter;
	}


	@Override
	public Toolkit getToolkit() {
		if (toolkit==null) {
			toolkit  = Parametros.toolkit;
		}
		return toolkit;
	}

	@Override
	public void setText(String t) {
				if ( t==null || t.equals("") ) {
					t = "0";
				}
				if (!t.contains("Infinity")) {
					logger.debug("Definido o texto "+t);
		
		
					char[] texto = t.toCharArray();
					StringBuilder sb = new StringBuilder();
					char ds = getDecimalFormatter().getDecimalFormatSymbols().getDecimalSeparator();
					boolean decimalFounded = false;
					for (int i = texto.length-1; i >= 0 ; i--) {
						char c = texto[i];
						if (c == '.' || c == ',') {
							if ( !decimalFounded ) { 
								if ( c == '.' ) {
									sb.insert( 0, ds );
									decimalFounded = true;
								} else {
									if (c == ds ) {
										sb.insert( 0, c );
										decimalFounded = true;
									}
								}
							}
						} else {
							sb.insert( 0, c );
						}
					}
					// Alterado por que dava problema ao EDITAR o campo.
					// Editando o campo com o valor "1.234,00" jogava "1,234"
					/*sb = new StringBuilder();
					for(char c : texto) {
						if (c == '.' || c == ',') {
							if ( c == '.' ) {
								sb.append( ds );
							} else {
								if (c == ds ) {
									sb.append( c );
								}
							}
						} else {
							sb.append( c );
						}
					}*/
					t = sb.toString();
		
					logger.debug("texto convertido "+t);
		
		
					double val=0;
					try {
						val = getDecimalFormatter().parse( t ).doubleValue();
						logger.debug("valor formatado "+val);
					} catch (ParseException e) {
						getToolkit().beep();
						logger.error("erro na formatação do valor", e);
					}
		
					logger.debug("mascara usada "+getMascara());
		
					isFormatting = true;
					try {
						super.setText( getDecimalFormatter().format( val ).toString() );
					} 
					finally {
						isFormatting = false;
					}
				} else {
					super.setText( t );
				}
		
				logger.debug("texto setado "+getText());
	}

	public double getValue() {

		double val = 0;
		try {
			val = getDecimalFormatter().parse( getText() ).doubleValue();

		} catch (ParseException e) {
			getToolkit().beep();
		}
		return val;

	}

	public BigDecimal getBigDecimalValue() {
		String str = "0";
		try {
			str = getDecimalFormatter().parse( getText() ).toString();
		} catch (ParseException e) {
			getToolkit().beep();
		}

		return new BigDecimal(str);
	}

	public void setBigDecimalValue(BigDecimal value) {
		setText( value != null ? value.toString() : BigDecimal.ZERO.toString());
	}



	public double getMaxDouble() {
		return maxDouble;
	}

	public void setMaxDouble(double maxDouble) {
		this.maxDouble = maxDouble;
	}

	public double getMinDouble() {
		return minDouble;
	}

	public void setMinDouble(double minDouble) {
		this.minDouble = minDouble;
	}

	public void setValue( String value ) {
		setText( value==null ? "0" : value );
	}

	public void setValue( Double value ) {
		setText( value==null? "0" : value.toString() );
	}

	public void setValue( BigDecimal value ) {
		setText( value==null? "0" : value.toString() );
	}

	@Override
	protected Document createDefaultModel() {
		return new NumericDocument();
	}

	@Override
	public void focusGained(FocusEvent e) {
		if ( !e.isTemporary() ) {
			this.setBackground( Parametros.getCorEditSelecionado() );
			this.setFont( Parametros.getFontEditSelecionado() );
			selectAll();
			
		}
	}


	@Override
	public void focusLost(FocusEvent e) {
		if ( !e.isTemporary() ) {
			this.setBackground( Parametros.getCorEdit() );
			this.setFont( Parametros.getFontEdit() );
			setText( getText() );
		}
	}


	@Override
	public synchronized void setEnabled(boolean enabled) {
		super.setEnabled(enabled);
//		setEditable( enabled );
//		setFocusable( enabled );
	}


	class NumericDocument extends PlainDocument {
		protected char signalNegative;

		public NumericDocument() {
			this( Parametros.getFormatSimbols().getMinusSign() );
		}

		public NumericDocument(char signalNegative) {
			this.signalNegative = signalNegative;
		}

		@Override
		public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
			if (!isFormatting && !str.contains("Infinity")) {
				char[] source = str.toCharArray();
				StringBuffer buf = new StringBuffer( source.length );
				boolean negativeValue  = false;
				for (int i = 0;i<source.length;i++) {
					if (isNegative() && offs==0 && source[i] != signalNegative && i == 0) {
						inputError(source[i]);
					} else if ( Character.isDigit( source[i] ) ) {
						if ( !overflowDecimais( offs ) ) {
							buf.append(  source[i] );
						} else {
							inputError( source[i] );
						}
					} else if ( source[i] == signalNegative ) {
						negativeValue = true;
					} else if ( (source[i]==','  || source[i]=='.' || source[i]==separadorDecimal)  && !contemSeparador() ) {
						if (getCasasDecimais() > 0) {
							buf.append( separadorDecimal );
						} else {
							inputError( source[i] );
							break; //sair para não incluir os numeros apos o separador decimal
						}
					} else {
						inputError(source[i]);
					}
				}
				super.insertString(offs, buf.toString(), a);
				if ( negativeValue ) {
					inverterSignal();
				}
			} else {
				super.insertString(offs, str, a);
			}
		}

		private boolean overflowDecimais(int offs) throws BadLocationException {
			boolean result = false;
			logger.debug( "testando casas decimais para não ultrapassarem o delimitado "+getCasasDecimais() );

			char[] texto = getText(0, getLength()).toCharArray();
			int length = texto.length;
			int indexSd = ArrayUtils.indexOf(texto, separadorDecimal);

			if (indexSd >= 0 && offs > indexSd) {
				logger.debug( "index "+indexSd+" do separador decimal "+separadorDecimal+"\n"+
						"casas decimais  "+ArrayUtils.toString( ArrayUtils.subarray(texto, indexSd, getLength()) ) );

				int lenghDecimaisOcupadas = 0;
				if (length > indexSd+1) {
					lenghDecimaisOcupadas = (length - (indexSd+1) ); 
				}

				logger.debug( "casas decimais de "+ArrayUtils.toString( texto )+" estão sendo usadas "+lenghDecimaisOcupadas);

				result = (lenghDecimaisOcupadas >= getCasasDecimais() );
			}

			return result;
		}

		private void inputError(char c) {
			getToolkit().beep();
		}

		protected void inverterSignal() throws BadLocationException {
			char[] chrs = getText(0, getLength()).toCharArray();

			if ( ArrayUtils.contains(chrs, signalNegative) ) {
				super.replace(0, 1, "", null);				
			} else {
				super.insertString(0, String.valueOf( signalNegative ), null);				
			}

		}

		protected boolean contemSeparador() throws BadLocationException {

			String str = getText(0, getLength());

			for(int i=0;i < getLength(); i++ ) {
				if ( str.charAt(i) == separadorDecimal) {
					return true;
				}
			}
			return false;
		}

		protected boolean isNegative() throws BadLocationException {
			char[] chrs = getText(0, getLength()).toCharArray();

			return ( ArrayUtils.contains(chrs, signalNegative) );
		}
	}


	public class InputVerifierController extends InputVerifier {
		@Override
		public boolean verify(JComponent input) {
			for(Object obj : inputVerifiers) {
				InputVerifier iv = (InputVerifier) obj;
				if ( iv != null && !iv.verify(input) ) {
					return false;
				}
			}
			return true;
		}
	}

	class MinMaxVerifier extends javax.swing.InputVerifier {
		public MinMaxVerifier(){
		}

		@Override
		public boolean verify(javax.swing.JComponent field) {
			JMLDoubleEdit de = (JMLDoubleEdit) field;
			double val = de.getValue();

			boolean resultA =  ( val >= minDouble ); 
			boolean resultB =  ( val <= maxDouble );

			if ( !resultA || !resultB ) {
				if (!resultA) {
//					fireMessageListeners( new MessageEvent("O valor inserido deve ser maior ou igual á "+minDouble+" ( "+val+" >= "+minDouble+" )") );
				} else {
//					fireMessageListeners( new MessageEvent("O valor inserido deve ser menor ou igual á "+maxDouble+" ( "+val+" <= "+maxDouble+" )") );
				}

				getToolkit().beep();
			}

			return resultA && resultB;
		}
	}
}
